package libnetdb.dm.assoc.util;
import java.util.*;
import java.io.*;

/**
 * This class provides a standard mechanism to parse . It parses the input
 * arguments to an key-value structure. So that we can get the value of a certen "key".
 * The following is an illustrative example of using this class:
 * <pre>
 *     public class Demo {
 *         public static void main(String argv[]) throws Exception{
 *             ArgumentDecoder decoder = new ArgumentDecoder(argv);
 *             if(decoder.containsKey("help")){
 *                  //show help message here
 *             }
 *             int n = Integer.parseInt(decoder.getValue("numb", "50"));
 *             // parse other parameters
 *         }
 *     }
 * </pre>
 *
 * If we execute this program demo by the following command:<br>
 *   <code>java Demo --numb 37 --names john mary </code><br>
 * Then <code>decoder.getValue("numb")</code> will get the string "37", and
 * <code>decoder.getValues("names")</code> will get an array of strings, {"john", "mary"}.
 * This class also provide a function <code>toProperties()</code> to transform
 * the class itself to a <code>java.util.Properties</code> form. It can also
 * load default value from a <code>Properties</code>.
 *
 * @author Cheng-Ru Lin
 * @version 1.0 2002/8
 */

public class ArgumentDecoder{
    private Hashtable key_map = new Hashtable();
    private static ResourceBundle boundle =
            ResourceBundle.getBundle(ArgumentDecoder.class.getName());

    /**
     * Creates a new <code>ArgumentDecoder</code> object which uses the
     * <code>Properties [</code> as the default values and parses the input
     * argument list <code>Stinrg[] argv</code> by using <code>Stinrg prefix</code>
     * as the key prefix.
     *
     * @param argv the argument list
     * @param prefix the prefix string of an key
     * @param p the default values
     */
    public ArgumentDecoder(String argv[], String prefix, Properties p) {
        if( p != null){
            Iterator it = p.entrySet().iterator();
            while(it.hasNext()){
                Map.Entry entry = (Map.Entry) it.next();
                key_map.put(entry.getKey(), new String[]{(String) entry.getValue()});
            }
        }
        parse(argv, prefix);
    }

    /**
     * Creates a new <code>ArgumentDecoder</code> object which parses the input
     * argument list <code>String[] argv</code> by using <code>String prefix</code>
     * as the key prefix.
     *
     * @param argv the argument list
     * @param prefix the prefix string of an key
     */
    public ArgumentDecoder(String argv[], String prefix) {
        this(argv, prefix, null);
    }

    /**
     * Creates a new <code>ArgumentDecoder</code> object which parses the input
     * argument list <code>String[] argv</code> by using <code>--</code>
     * as the key prefix.
     *
     * @param argv the argument list
     */
    public ArgumentDecoder(String argv[]){
        this(argv, "--", null);
    }

    private void parse(String argv[], String prefix){
        int idx, last, skip = prefix.length();
        for(idx=0; idx < argv.length; ++idx)
            if(argv[idx].startsWith(prefix)) break;
        while(idx < argv.length){
            for(last=idx+1;last<argv.length;++last)
                if(argv[last].startsWith(prefix)) break;
            String value[] = new String[last-idx-1];
            System.arraycopy(argv, idx+1, value, 0, last-idx-1);
            key_map.put(argv[idx].substring(skip), value);
            idx = last;
        }
    }

    /**
     * Returns the first value of the string array associated with the specified key.
     * Returns <code>null</code> if there is no such key or the string array size is 0.
     *
     * @param key key whose associated value is to be returned.
     * @return the first value of the string array associated with the specified key.
     */
    public String getValue(String key){
        String value[] = getValues(key);
        return value!=null && value.length > 0 ? value[0] : null;
    }

    /**
     * Returns the first value of the string array associated with the specified key.
     * Returns <code>defaultValue</code> if there is no such key or the string
     * array size is 0.
     *
     * @param key key whose associated value is to be returned.
     * @param defaultValue value to return if cannot find the assoicated value
     * @return the first value of the string array associated with the specified key or
     *     <code>defaultValue</code> if such value is not exists.
     */
    public String getValue(String key, String defaultValue){
        String value = getValue(key);
        return value == null ? defaultValue : value;
    }

    /**
     * Prompts users to input some required value on the console.
     *
     * @param required keys of required entries
     * @throws IOException if fail to read input from <code>System.in</code>
     * @throws UserCancelException if user stop to input required value
     */
    public void prompt(String required[]) throws IOException, UserCancelException{
        String promptStr = boundle.getString("PromptString");
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        for(int i=0;i<required.length;++i){
            if(!key_map.containsKey(required[i])){
                String line;
                System.out.print(promptStr.replaceFirst("<key>",required[i]));
                line = br.readLine();
                if(line == null) throw new UserCancelException();
                key_map.put(required[i], new String[]{line.trim()});
            }
        }
    }

    /**
     * Returns the associate string array of the specified key. Return <code>null</code>
     * if such array doesn't exist in this decoder.
     *
     * @param key key whose associated value is to be returned
     * @return the string array to which this decoder associate with the specified key or
     *   <code>null</code> if this decoder contains no such string array.
     */
    public String[] getValues(String key){
        return (String[]) key_map.get(key);
    }

    /**
     * Returns true if this decoder contains an entry for the specified key.
     * More formally, returns true if and only if this decoder contains at an entry for
     * a key k such that (key==null ? k==null : key.equals(k)).
     *
     * @param key key whose presence in this decoder is to be tested.
     * @return true if this decoder contains an entry for the specified key.
     */
    public boolean containsKey(String key){
        return key_map.containsKey(key);
    }

    /**
     * Returns a <code>Properties</code> which has the same key-value pair as the decoder.
     * The returned <code>Properties</code> maps the key to only the first value of the
     * associated string array.
     *
     * @return a <code>Properties</code> has the same key-vlaue pair as this decoder.
     */
    public Properties toProperties(){
        Properties p = new Properties();
        Iterator it = entrySet().iterator();
        while(it.hasNext()){
            Map.Entry entry = (Map.Entry) it.next();
            String key = (String) entry.getKey();
            String value[] = (String[]) entry.getValue();
            if(value.length > 1){
                p.setProperty(key + ".length", Integer.toString(value.length));
                for(int i=0;i<value.length;++i)
                    p.setProperty(key + "[" + i + "]", value[i]);
            }
            p.setProperty(key, value[0]);
        }
        return p;
    }

    /**
     * Returns a set view of the mappings contained in this decoder. Each element in
     * the returned set is a Map.Entry. The returned set is read only.
     * @return a set view of the mappings contained in this decoder.
     */
    public Set entrySet(){
        return Collections.unmodifiableSet(key_map.entrySet());
    }
}